using System;
using System.Collections.Generic;
using FibMatrix.Task;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Profiling;

namespace FibMatrix
{
	/// <summary>
	/// webgl平台（微信小游戏）负责下载读取bundle，不负责保存，保存由浏览器负责，
	/// 微信小游戏要求url中包含指定字符串，比如StreamingAssets，就自动缓存
	/// </summary>
	[FM_Mono.IgnoreGen]
	public class AsyncLoadBundleFromRemotePipe : IResourceLoadPipe
	{
		class UnityWebRequestAsyncWork:IAsyncBase
		{
			public bool isCompleted
			{
				get
				{
					return _completed;
				}
			}
			private Action<IAsyncBase> _asyncCallback;

			public event Action<IAsyncBase> completeCallback
			{
				add
				{
					this._asyncCallback += value;
				}

				remove
				{
					this._asyncCallback -= value;
				}
			}

			private UnityWebRequestAsyncOperation _asyncOperation;

			private BundleInfo _bundleInfo;

			private Dictionary<string, UnityWebRequestAsyncWork> _workMap;

			private string _errorDesc;
			
			public string errorDesc
			{
				get
				{
					return _errorDesc;
				}
			}

			private bool _completed = false;

			private int _refCount = 0;

			public int refCount => _refCount;

			public void AddRef()
			{
				++_refCount;
			}

			public void DecRef()
			{
				if (_refCount > 0)
				{
					--_refCount;
					if (_refCount == 0)
					{
						Dispose();
					}
				}
			}

			public string url { get; private set; }
			
			public UnityWebRequestAsyncWork(Dictionary<string, UnityWebRequestAsyncWork> workMap, string cdn, BundleInfo bundleInfo)
			{
				_workMap = workMap;
				_bundleInfo = bundleInfo;
				url = ResourceHelper.GetAssetURL(cdn, bundleInfo.url, bundleInfo.md5);
				// Debug.Log("Async load from " + url);
				try
				{
					var uri = new Uri(url);
					var request = !_bundleInfo.node.isAssetBundleFile ? UnityWebRequest.Get(uri) : 
#if UNITY_WEBGL && !UNITY_EDITOR
						 WeChatWASM.WXAssetBundle.GetAssetBundle(uri.AbsoluteUri);
#else
						 UnityWebRequestAssetBundle.GetAssetBundle(uri);
#endif
					_asyncOperation = request.SendWebRequest();
					_asyncOperation.completed += OnRequestComplete;
				}
				catch (Exception e)
				{
					Debug.LogError(url);
					Debug.LogException(e);
				}
			}

			private void OnRequestComplete(AsyncOperation op)
			{
				if (_asyncOperation == op)
				{
					_completed = true;
					var asOp = _asyncOperation;
					_asyncOperation = null;
					asOp.completed -= OnRequestComplete;
					var request = asOp.webRequest;
					if (request.isHttpError || request.isNetworkError)
					{
						_errorDesc = request.error;
					}
					else if ((_bundleInfo.bundleHandle == null || _bundleInfo.bundleHandle.bundle == null) &&
					         _bundleInfo.node.isAssetBundleFile)
					{
						// 后缀判断是否为AB，非AB只用pipeline下载不加载，留给业务读文件
						Profiler.BeginSample("WebRequest.ab");
						Profiler.BeginSample(_bundleInfo.url);
						_bundleInfo.bundleHandle = BundleLoadManager.instance.AddBundle(_bundleInfo.url, 
#if UNITY_WEBGL && !UNITY_EDITOR
							(request.downloadHandler as WeChatWASM.DownloadHandlerWXAssetBundle).assetBundle);
#else
							(request.downloadHandler as DownloadHandlerAssetBundle).assetBundle);
#endif
						Profiler.EndSample();
						Profiler.EndSample();
                        AssetActionLogger.RecordAction(null, _bundleInfo.url, AssetBundleActionType.BundleLoadAsync);
					}
					
					var callback = _asyncCallback;
					_asyncCallback = null;
					callback?.Invoke(this);
				}
			}

			private void Dispose()
			{
				if (_asyncOperation != null)
				{
					var asOp = _asyncOperation;
					_asyncOperation = null;
					asOp.completed -= OnRequestComplete;
					asOp.webRequest.Dispose();
				}

				_asyncCallback = null;

				_workMap.Remove(_bundleInfo.url);
				_workMap = null;
			}
		}

		private string _cdn;

		private Dictionary<string, UnityWebRequestAsyncWork> _workMap =
			new Dictionary<string, UnityWebRequestAsyncWork>();
		public AsyncLoadBundleFromRemotePipe(string cdn)
		{
			if (ResourceDevCfg.Instance.IsDebugStreamPlay())
			{
				cdn = ResourceDevCfg.Instance.GetDebugCdn();
			}
			this._cdn = cdn;
		}
		
		public FMTask Handle(IResourcePipelineContext rawContext, out ResourceError error)
		{
			Profiler.BeginSample("AsyncLoadBundleFromRemotePipe.Handle");
			error = ResourceError.None;
			ResourcePipelineContext context = rawContext as ResourcePipelineContext;
			AssetBundleLoadItem mainHint = context.item as AssetBundleLoadItem;

			List<BundleInfo> dependence = ResourceHelper.GetFixedFlatDependences(mainHint.bundleInfo);

			FMTaskList taskList = null;
			for (int i = 0, l = dependence != null ? dependence.Count : 0; i < l; ++i)
			{
				BundleInfo hint = dependence[i];

				// 小游戏不用判断本地有没有，只管请求，由浏览器负责是读缓存还是下载。
				// 编辑器中每次都会下载，不保存
				if (
					//hint.urlMode == AssetBundleUrlMode.Remote && 
					(hint.bundleHandle == null || hint.bundleHandle.bundle == null))
				{
					taskList = taskList ?? new FMTaskList("TaskList", false);
					//Debug.Log("Async load dep " + hint.url);
					taskList.Add(PooledAsyncFMTask.Allocate(hint, this, GenerateDownloadAsyncWork,
						GetDownloadAsyncWorkError, ProcessDownloadAsyncWorkResult));
				}
			}

			if (taskList != null)
			{
				taskList.asyncMode = true;
				taskList.Begin();
			}

			Profiler.EndSample();
			return taskList;
		}

		private static IAsyncBase GenerateDownloadAsyncWork(PooledAsyncFMTask task)
		{
			Profiler.BeginSample("GenerateDownloadAsyncWork");
			var host = task.context2 as AsyncLoadBundleFromRemotePipe;
			var workMap = host._workMap;
			var item = task.context as BundleInfo;
			if (!workMap.TryGetValue(item.url, out var work))
			{
				work = new UnityWebRequestAsyncWork(workMap, host._cdn, item);
				workMap.Add(item.url, work);
			}
			work.AddRef();

			Profiler.EndSample();
			return work;
		}

		private static ResourceError GetDownloadAsyncWorkError(PooledAsyncFMTask task, IAsyncBase asyncWork)
		{
			var work = (asyncWork as UnityWebRequestAsyncWork);
			string errorDesc = asyncWork != null ? work.errorDesc : null;
			if (string.IsNullOrEmpty(errorDesc))
				return ResourceError.None;
			else
			{
				Debug.LogError($"{errorDesc}, {work.url}");
				return ResourceError.DownloadAssetError;
			}
		}

		private static void ProcessDownloadAsyncWorkResult(PooledAsyncFMTask task, IAsyncBase asyncWork)
		{
			var item = task.context as BundleInfo;
			// if (item.urlMode == AssetBundleUrlMode.Remote)
			{
				//Debug.Log("Async load dep complete " + item.url);
				var work = asyncWork as UnityWebRequestAsyncWork;
				work.DecRef();
			}
		}
	}
}